(ns server.server
  "This namespace contains the methods that pass let create, delete, update, describe and select the values from the client. "
  (:gen-class)
  (:use server.dbm (clojure.contrib str-utils))
  (:import (java.net Socket ServerSocket)
	   (java.io PrintWriter BufferedReader InputStreamReader)
	   (java.security MessageDigest))
  (:require clojure.contrib.gen-html-docs))
  
(def route "./")



(defn create
  "Gets the data from the client and pass it to the database manager to create a new record"
  [database data]
  (create-record (.concat route (.concat database ".db")) data))

(defn delete
  "Gets the record to delete from the client and pass it to the database manager to delete a record"
  [database RecNo]
  (delete-record (.concat route (.concat database ".db")) RecNo))

(defn update
  "Gets the record to update from the client and pass it to the database manager to update a record"
  [database RecNo data]
  (update-record (.concat route (.concat database ".db")) RecNo data))

(defn describe
  "Returns the caracteristics of a database, in the number of fields, names of fields, and number of records that are not deleted."
  [database]
  (let [file-name (.concat route (.concat database ".db"))
	number (get-fields-number file-name)
	fields (get-fields-names file-name)
	records (records-number-not-deleted file-name)]
    (conj [] number fields records)))
  
(defn select
  "Gets all the records that are not deleted to the client"
  [database]
  (read-records-not-deleted (.concat route (.concat database ".db"))))


(defn serialize
	"Converts structures into strings"
	[data]
	(binding[*print-dup* true]
  		(print-str data))
)

(defn hashDB
  "Calculate the md5 one way hash of the database"
  [database]
  (let [md (doto (MessageDigest/getInstance "MD5"))]
    (.update md (.getBytes (serialize (select database))))
    (let [result (.digest md)]
     (str-join "" (map #(Integer/toHexString (bit-and % 0xff)) result)))))


(defn out-of-date
  "Checks with the client, if it is out of date or can proceed"
  [database hashRec]
  (if (.equals hashRec (hashDB database))
    "up-to-date"
    "out-of-date"))
  

(defn select-net-send
  "sends the records"
  [database output]
  (let [records (serialize (select database))]
    (.println output records)
    (.flush output)
    )
)
 
(defn create-net-send
  "creates a record"
  [data output]
  (let [[database dict] (read-string data)]
    (create database dict)
    )
)


(defn update-net-send
  "updates the record"
  [data output]
  (let [[database num dict] (read-string data)]
    (update database num dict)
   )
)

(defn delete-net-send
  "deletes a record"
  [data output]
  (let [[database recno] (read-string data)]
    (delete database recno)
    )
)

(defn describe-net-send
  "Performs a describe in the DB and sends it through the socket"
  [database output]
    (let [fields (serialize (describe database))]
      (.println output fields)
    )
)

(defn out-of-date-net-send
  "Sends the result of the out-of-date check to the client"
  [data output]
  (let [[database hashRec] (read-string data)]
    (.println output (out-of-date database hashRec))
    (.flush output)))
    
(defn connect-test
  "Permits to check the connection"
  [input output]   
    (.println output "if you see this, the connection is working")
    (.flush output))

(defn manager
  "Manages the petitions from the client, so it executes the correct operation"
  [_ socket]
  (with-open [input (BufferedReader. (InputStreamReader. (.getInputStream socket)))
	      output (PrintWriter. (.getOutputStream socket))]
    (let [database (.readLine input)
	  action (.readLine input)]
      (when (.equals action "test")
	(.flush output)
	(connect-test input output))
      (when (.equals action "describe")
	(.flush output)
	(describe-net-send database output))
      (when (.equals action "select")
	(.flush output)
	(select-net-send database output))
      (when (.equals action "update")
	(.flush output)
	(update-net-send database output))
      (when (.equals action "create")
	(.flush output)
	(create-net-send database output))
      (when (.equals action "delete")
	(.flush output)
	(delete-net-send database output))
      (when (.equals action "out-of-date")
	(.flush output)
	(out-of-date-net-send database output))
	

)))



   
(defn server-start
  "Make the server start attending the connections"
  [port]
  (let [server (ServerSocket. port)
	agent (agent nil)]
	(println "To abort the server, Ctrl+C")
    (loop []
      (println "Waiting for connections")
      (let [socket (.accept server)]
	(send-off agent manager socket))
	(recur))))


